package robust.gradle.plugin

import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import com.meituan.robust.Constants
import javassist.ClassPool
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import robust.gradle.plugin.asm.AsmInsertImpl
import robust.gradle.plugin.javaassist.JavaAssistInsertImpl

import java.util.zip.GZIPOutputStream


class RobustTransform extends Transform {

    Project project
    def robust
    static Logger logger
    private static List<String> hotfixPackageList = new ArrayList<>();
    private static List<String> hotfixMethodList = new ArrayList<>();
    private static List<String> exceptPackageList = new ArrayList<>();
    private static List<String> exceptMethodList = new ArrayList<>();
    private static boolean isHotfixMethodLevel = false
    private static boolean isExceptMethodLevel = false

    //ASM字节码插桩
    private static boolean useASM = true

    //针对Java8级别的Lambda表达式，编译为private级别的javac函数，此时由开发者决定是否进行插桩处理
    private static boolean isForceInsertLambda = false

    InsertcodeStrategy insertcodeStrategy;

    RobustTransform(Project project) {
        this.project = project
        initConfig()
    }

    /**
     * 插件的名称，在gradle task中显示
     */
    @Override
    String getName() {
        return "robust"
    }

    /**
     * 你想要处理的文件
     */
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    /**
     * 你想要处理的范围
     */
    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    /**
     * 是否增量编译
     */
    @Override
    boolean isIncremental() {
        return false
    }

    /**
     * 解析robust.xml配置文件
     */
    def initConfig() {
        logger = project.logger
        robust = new XmlSlurper().parse(new File("${project.projectDir}/${Constants.ROBUST_XML}"))
        hotfixPackageList = new ArrayList<>()
        hotfixMethodList = new ArrayList<>()
        exceptPackageList = new ArrayList<>()
        exceptMethodList = new ArrayList<>()
        isHotfixMethodLevel = false
        isExceptMethodLevel = false

        //热修复的包名
        for (name in robust.packname.name) {
            hotfixPackageList.add(name.text())
            logger.quiet("hotfixPackageList -> $name")
        }

        //不需要robust插入的包名
        for (name in robust.exceptPackname.name) {
            exceptPackageList.add(name.text())
            logger.quiet("exceptPackageList -> $name")
        }

        //这个暂时没有给出示例
        for (name in robust.hotfixMethod.name) {
            hotfixMethodList.add(name.text())
            logger.quiet("hotfixMethodList -> $name")
        }

        //这个暂时没有给出示例
        for (name in robust.exceptMethod.name) {
            exceptMethodList.add(name.text())
            logger.quiet("exceptMethodList -> $name")
        }

        if (null != robust.switch.filterMethod && "true" == String.valueOf(robust.switch.turnOnHotfixMethod.text())) {
            isHotfixMethodLevel = true
        }
        logger.quiet("isHotfixMethodLevel -> $isHotfixMethodLevel")

        //支持ASM字节码插桩
        if (null != robust.switch.useAsm && "false" == String.valueOf(robust.switch.useAsm.text())) {
            useASM = false
        } else {
            //默认使用asm
            useASM = true
        }
        logger.quiet("useASM -> $useASM")

        if (null != robust.switch.filterMethod && "true" == String.valueOf(robust.switch.turnOnExceptMethod.text())) {
            isExceptMethodLevel = true
        }
        logger.quiet("isExceptMethodLevel -> $isExceptMethodLevel")

        //针对Java8级别的Lambda表达式，编译为private级别的javac函数，此时由开发者决定是否进行插桩处理
        if (robust.switch.forceInsertLambda != null && "true" == String.valueOf(robust.switch.forceInsertLambda.text()))
            isForceInsertLambda = true
        else
            isForceInsertLambda = false
        logger.quiet("isForceInsertLambda -> $isForceInsertLambda")

    }

    @Override
    void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
        logger.quiet '================robust start================'
        def startTime = System.currentTimeMillis()

        // 1、删除之前的输出
        outputProvider.deleteAll()

        /*
         * If the format is Format.JAR then the result is a file representing the jar to create.
         * 如果格式为“格式”。JAR，那么结果就是一个表示要创建的JAR的文件。
         */
        File jarFile = outputProvider.getContentLocation("main", getOutputTypes(), getScopes(), Format.JAR)
        if(!jarFile.getParentFile().exists()){
            jarFile.getParentFile().mkdirs()
        }
        if(jarFile.exists()){
            jarFile.delete()
        }

        ClassPool classPool = new ClassPool()
        //加入android.jar，不然找不到android相关的所有类
        project.android.bootClasspath.each {
            def path=(String) it.absolutePath
            logger.quiet "bootClassPath path=$path"
            classPool.appendClassPath(path)
        }

        // class转化为javassist中的CtClass，以便后面的操作，既然已经输入class文件全部提出来了，那么接下来就是操作字节码了
        def box = ConvertUtils.toCtClasses(inputs, classPool)

        def cost = (System.currentTimeMillis() - startTime) / 1000

        if (useASM) {
            insertcodeStrategy = new AsmInsertImpl(hotfixPackageList, hotfixMethodList, exceptPackageList, exceptMethodList, isHotfixMethodLevel, isExceptMethodLevel, isForceInsertLambda);
        } else {
            insertcodeStrategy = new JavaAssistInsertImpl(hotfixPackageList, hotfixMethodList, exceptPackageList, exceptMethodList, isHotfixMethodLevel, isExceptMethodLevel, isForceInsertLambda);
        }

        insertcodeStrategy.insertCode(box, jarFile)
        writeMap2File(insertcodeStrategy.methodMap, Constants.METHOD_MAP_OUT_PATH)


        logger.quiet "===robust print id start==="
        for (String method : insertcodeStrategy.methodMap.keySet()) {
            int id = insertcodeStrategy.methodMap.get(method);
            System.out.println("key is   " + method + "  value is    " + id)
        }
        logger.quiet "===robust print id end==="

        cost = (System.currentTimeMillis() - startTime) / 1000
        logger.quiet "robust cost $cost second"
        logger.quiet '================robust   end================'
    }


    private void writeMap2File(Map map, String path) {
        File file = new File(project.buildDir.path + path)
        if (!file.exists() && (!file.parentFile.mkdirs() || !file.createNewFile())) {
//            logger.error(path + " file create error!!")
        }
        FileOutputStream fileOut = new FileOutputStream(file);

        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(byteOut);
        objOut.writeObject(map)
        //gzip压缩
        GZIPOutputStream gzip = new GZIPOutputStream(fileOut);
        gzip.write(byteOut.toByteArray())
        objOut.close();
        gzip.flush();
        gzip.close();
        fileOut.flush()
        fileOut.close()
    }
}